/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice.persistence.derivative;

import org.proteusframework.core.base.Namespace;
import org.proteusframework.core.util.Assert;
import org.proteusframework.platformservice.persistence.api.messagebean.IMessageBeanProperty;
import org.proteusframework.platformservice.persistence.c14n.C14NFactory;
import org.proteusframework.platformservice.persistence.messagebean.DataType;

import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import java.util.logging.Logger;

import static org.proteusframework.platformservice.persistence.c14n.IC14NHandler.PACKED_BLOB_PREFIX_LEN;
import static org.proteusframework.platformservice.persistence.c14n.IC14NHandler.PACKED_BLOB_SUFFIX_LEN;

/**
 * @author Tacoma Four
 */
final class DataInputStreamDerivativeConverter implements IDerivativeConverter<DataInputStream>
{
    private static final Logger logger = Logger.getLogger(DataInputStreamDerivativeConverter.class.getCanonicalName());

    @Override
    public void convert(DataInputStream targetDerivative, DataType dataType, Object messageBean, Object... parameters)
    {
        Object value = null;

        Assert.parameterNotNull(parameters, "Parameter 'parameters' must include a Method that points to the setter");
        Assert.equals(2, parameters.length, "Parameter 'parameters' must include two parameters, a Method suitable for invocation and the IMessageBeanProperty");

        Method method = (Method) parameters[0];
        IMessageBeanProperty beanProperty = (IMessageBeanProperty) parameters[1];

        try
        {
            boolean isNullFlag = targetDerivative.readBoolean();
            logger.fine("isNullFlag: " + isNullFlag);

            if (!isNullFlag)
            {
                switch (dataType)
                {
                    case BigDecimalType:
                        logger.fine("read UTF");
                        value = new BigDecimal(targetDerivative.readUTF());
                        break;
                    case BigIntegerType:
                        logger.fine("read LONG");
                        value = targetDerivative.readLong();
                        break;
                    case BooleanType:
                        logger.fine("read Boolean");
                        value = targetDerivative.readBoolean();
                        break;
                    case ByteArrayType:
                        int byteArrayLength = targetDerivative.readInt();
                        logger.fine("Retrieved a length of " + byteArrayLength);
                        byte[] bytes = new byte[byteArrayLength];
                        targetDerivative.read(bytes);
                        logger.fine("read Bytes");
                        value = bytes;
                        break;
                    case ByteType:
                        logger.fine("read Byte");
                        value = targetDerivative.readByte();
                        break;
                    case CharArrayType:
                        logger.fine("read UTF");
                        value = targetDerivative.readUTF().toCharArray();
                        break;
                    case CharType:
                        logger.fine("read UTF");
                        value = targetDerivative.readUTF().toCharArray()[0];
                        break;
                    case DateType:
                        logger.fine("read Long");
                        value = new Date(targetDerivative.readLong());
                        break;
                    case DoubleType:
                        logger.fine("read Double");
                        value = targetDerivative.readDouble();
                        break;
                    case EnumType:
                        logger.fine("read Int");
                        int enumOrdinal = targetDerivative.readInt();
                        String enumClass = targetDerivative.readUTF();
                        try
                        {
                            Class c = Class.forName(enumClass);
                            value = c.getEnumConstants()[enumOrdinal];
                        } catch (ClassNotFoundException e)
                        {
                            e.printStackTrace();
                        }
                        break;
                    case FloatType:
                        logger.fine("read Float");
                        value = targetDerivative.readFloat();
                        break;
                    case IntegerType:
                        logger.fine("read Int");
                        value = targetDerivative.readInt();
                        break;
                    case LongType:
                        logger.fine("read Long");
                        value = targetDerivative.readLong();
                        break;
                    case NamespaceType:
                        logger.fine("read UTF");
                        value = Namespace.fromCanonical(targetDerivative.readUTF());
                        break;
                    case ShortType:
                        logger.fine("read SHORT");
                        value = targetDerivative.readShort();
                        break;
                    case StringType:
                        logger.fine("read UTF");
                        value = targetDerivative.readUTF();
                        break;
                    case TimeType:
                        logger.fine("read LONG");
                        value = new Time(new Date(targetDerivative.readLong()).getTime());
                        break;
                    case TimestampType:
                        logger.fine("read LONG");
                        value = new Timestamp(new Date(targetDerivative.readLong()).getTime());
                        break;
                    case URLType:
                        logger.fine("read UTF");
                        value = new URL(targetDerivative.readUTF());
                        break;
                    case UUIDType:
                        logger.fine("read UTF");
                        value = UUID.fromString(targetDerivative.readUTF());
                        break;
                    case XmlType:
                        logger.fine("read UTF");
                        value = targetDerivative.readUTF();
                        break;
                    default:
                        int length = targetDerivative.readInt();
                        logger.fine("Retrieved a length of " + length);
                        byte[] arrayBytes = new byte[length];
                        targetDerivative.read(arrayBytes);
                        logger.fine("read BYTES");

                        String headerPrefix = new String(arrayBytes, 0, PACKED_BLOB_PREFIX_LEN);
                        int headerDataType = Integer.valueOf(new String(arrayBytes, PACKED_BLOB_PREFIX_LEN, 2));
                        String headerSuffix = new String(arrayBytes, PACKED_BLOB_PREFIX_LEN + 2, PACKED_BLOB_SUFFIX_LEN);

                        byte[] rawBytes = Arrays.copyOfRange(arrayBytes, PACKED_BLOB_PREFIX_LEN + 2 + PACKED_BLOB_SUFFIX_LEN, arrayBytes.length);

                        value = C14NFactory.getHandler(dataType).fromC14N(rawBytes);
                }
            }

            method.invoke(messageBean, value);
        } catch (IOException e)
        {
            e.printStackTrace();
        } catch (InvocationTargetException e)
        {
            e.printStackTrace();
        } catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }
    }
}
